refactor(workspace): timeline API overhaul and extension system cleanup#1528
Merged
refactor(workspace): timeline API overhaul and extension system cleanup#1528
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overhauls the Timeline from a bag of standalone functions into a cohesive object API, and cleans up the extension system's dual-scope registration model. What started as a snapshot restore bug led to pulling on threads until the whole timeline module was restructured.
Why the timeline needed this
The old timeline had two problems. First,
DocumentHandlewas a wrapper aroundTimelinethat added nothing—consumers had to go through the handle to reach timeline methods, but the handle's only job was forwarding calls. Second, the public API surface was too wide:readEntry,pushText,pushRichtext,pushSheet,parseSheetFromCsv,restoreFromSnapshotwere all standalone exports that leaked implementation details.The snapshot restore bug was the catalyst. Server-side
applySnapshot()was a silent no-op (CRDT idempotency means applying an old state to a newer doc changes nothing), and the richtext path destroyed formatting by extracting text-only content fromXmlFragment. Fixing this properly requiredrestoreFromSnapshotto become a method that understood the timeline's internal state—which meant the standalone-function architecture had to go.Mode-aware write
The old API had
writeText()for text content. Nowwrite()is mode-aware—it checks the current entry type and either replaces in-place (same type, no new entry,observe()doesn't fire) or pushes a new entry (type change,observe()fires).writeSheet()follows the same pattern:Snapshot restore
Moved entirely to client-side. The key fix for richtext: deep-clone the
XmlFragmenttree instead of extracting text.Extension system: dual-scope registration
Extensions that work on both the workspace Y.Doc and content document Y.Docs (persistence, sync) previously had no type-safe way to register once for both scopes. The factory signature for workspace extensions (
ExtensionContextwith tables, kv, awareness) is wider than for document extensions (DocumentContextwith timeline, ydoc).SharedExtensionContextis the intersection—the fields available in both scopes:withExtension()becomes thin sugar—it registers the same factory for both scopes, constrained toSharedExtensionContext. If you need workspace-only or document-only fields, use the scoped methods:The sync extension now returns scoped factories:
Other extension changes
timelinewas added toDocumentContextso document extension factories can bind to the content timeline directly. LIFO teardown logic (destroyLifo/startDestroyLifo) was extracted tolifecycle.tsas shared primitives used by bothcreateWorkspaceandcreateDocuments. The lifecycle.ts module docstring was rewritten—it still described a stale "Providers vs Extensions" architecture with a nonexistentProviderFactorytype.DOCUMENTS_ORIGINis now exported from@epicenter/workspace. It's a sentinel symbol for distinguishing auto-bumps (document edit → rowupdatedAtupdate) from user-initiated row changes intable.observe()callbacks.Smaller changes
table.delete()returnsvoidinstead ofDeleteResult. The discriminated union added no value—Y.Map.delete() is fire-and-forget, and callers never branched on the result.table.observe()callbacks now receive typedTransactionMeta(withorigin) instead of rawunknown.~30 skill files got metadata blocks, cross-reference callouts, and "When to Apply" sections. A refactoring methodology skill was added.